/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.services.project;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.cfg.RowLockProvider;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.entities.beans.table.TableBean;
import cn.easyplatform.interceptor.CommandContext;
import org.apache.shiro.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
class StdRowLockProvider implements RowLockProvider {

    private final static Logger log = LoggerFactory.getLogger(StdRowLockProvider.class);

    private CommandContext cc;

    private Cache<Serializable, LockInfo> cache;

    private ProjectService ps;

    private Lock mutex = new ReentrantLock();

    StdRowLockProvider(ProjectService ps) {
        this.ps = ps;
        this.cache = ps.getCache("RowLocker");
    }

    void setCommandContext(CommandContext cc) {
        this.cc = cc;
    }

    @Override
    public String lock(TableBean tb, RecordContext record) {
        mutex.lock();
        try {
            StringBuilder sb = new StringBuilder(tb.getId()).append(":");
            for (int i = 0; i < tb.getKey().size(); i++) {
                Object key = record.getFieldValue(tb.getKey().get(i));
                sb.append(key == null ? "" : key);
            }
            String key = sb.toString();
            sb = null;
            log.debug("lock->" + key);
            String userId = record.getParameterAsString("720");
            String taskId = record.getParameterAsString("800");
            LockInfo lock = cache.get(key);
            if (lock != null) {
                if (!lock.getUserId().equals(userId))
                    throw new EasyPlatformWithLabelKeyException(
                            "dao.record.user.locked", lock.getUserId());
                else if (!lock.getTaskId().equals(taskId))
                    throw new EasyPlatformWithLabelKeyException(
                            "dao.record.task.locked", taskId);
            } else
                cache.put(
                        key,
                        new LockInfo(userId, taskId, record
                                .getParameterAsInt("828"), key).setFunId(record.getParameterAsString("801")));
            return key;
        } finally {
            mutex.unlock();
        }
    }

    @Override
    public String lock(String tableId, Object... keys) {
        mutex.lock();
        try {
            StringBuilder sb = new StringBuilder(tableId).append(":");
            for (Object key : keys) {
                if (key instanceof FieldDo)
                    key = ((FieldDo) key).getValue();
                sb.append(key == null ? "" : key);
            }
            String key = sb.toString();
            sb = null;
            log.debug("lock->" + key);
            WorkflowContext ctx = cc.getWorkflowContext();
            String userId = cc.getUser().getId();
            String taskId = ctx.getId();
            LockInfo lock = cache.get(key);
            if (lock != null) {
                if (!lock.getUserId().equals(userId))
                    throw new EasyPlatformWithLabelKeyException(
                            "dao.record.user.locked", lock.getUserId());
                else if (!lock.getTaskId().equals(taskId))
                    throw new EasyPlatformWithLabelKeyException(
                            "dao.record.task.locked", taskId);
            } else
                cache.put(
                        key,
                        new LockInfo(userId, taskId, ctx
                                .getParameterAsInt("828"), key).setFunId(ctx.getParameterAsString("801")));
            return key;
        } finally {
            mutex.unlock();
        }
    }

    @Override
    public void exclusiveLock(String tableId, Object... keys) {
        mutex.lock();
        try {
            StringBuilder sb = new StringBuilder(tableId).append(":");
            for (Object key : keys) {
                if (key instanceof FieldDo)
                    key = ((FieldDo) key).getValue();
                sb.append(key == null ? "" : key);
            }
            String key = sb.toString();
            sb = null;
            log.debug("lock->" + key);
            WorkflowContext ctx = cc.getWorkflowContext();
            String userId = cc.getUser().getId();
            String taskId = ctx.getId();
            LockInfo lock = cache.get(key);
            if (lock != null) {
                if (!lock.getUserId().equals(userId))
                    throw new EasyPlatformWithLabelKeyException(
                            "dao.record.user.locked", lock.getUserId());
                else if (!lock.getTaskId().equals(taskId))
                    throw new EasyPlatformWithLabelKeyException(
                            "dao.record.task.locked", taskId);
            } else {
                int layer = ctx
                        .getParameterAsInt("828");
                unlock(taskId, layer);
                cache.put(
                        key,
                        new LockInfo(userId, taskId, layer, key).setFunId(ctx.getParameterAsString("801")));
            }
        } finally {
            mutex.unlock();
        }
    }

    @Override
    public void unlock(String tableId, Object... keys) {
        mutex.lock();
        try {
            StringBuilder sb = new StringBuilder(tableId).append(":");
            for (Object key : keys) {
                if (key instanceof FieldDo)
                    key = ((FieldDo) key).getValue();
                sb.append(key == null ? "" : key);
            }
            String key = sb.toString();
            sb = null;
            log.debug("unlock->" + key);
            cache.remove(key);
        } finally {
            mutex.unlock();
        }
    }

    @Override
    public void unlock(String taskId, int layer) {
        mutex.lock();
        try {
            String userId = cc.getUser().getId();
            Iterator<LockInfo> itr = cache.values().iterator();
            while (itr.hasNext()) {
                LockInfo li = itr.next();
                if (li.getUserId().equals(userId)
                        && taskId.equals(li.getTaskId())
                        && li.getLayer() >= layer) {
                    cache.remove(li.getKey());
                    log.debug("unlocked->" + li);
                }
            }
        } finally {
            mutex.unlock();
        }
    }

    @Override
    public void unlock(String key) {
        mutex.lock();
        try {
            cache.remove(key);
        } finally {
            mutex.unlock();
        }
    }

    @Override
    public void clear() {
        clear(cc.getUser());
    }

    @Override
    public void clear(UserDo user) {
        mutex.lock();
        try {
            Iterator<LockInfo> itr = cache.values().iterator();
            while (itr.hasNext()) {
                LockInfo li = itr.next();
                if (li.getUserId().equals(user.getId())) {
                    cache.remove(li.getKey());
                    log.debug("clear->" + user.getId());
                }
            }
        } finally {
            mutex.unlock();
        }
    }

    void destory() {
        mutex.lock();
        try {
            cache.clear();
            ps.removeCache("RowLocker");
            cache = null;
        } finally {
            mutex.unlock();
        }
    }
}
